package com.hzgj.bcl.util.jmx;

import com.google.common.collect.Maps;
import com.hzgj.bcl.util.jmx.annotation.MBean;
import com.hzgj.bcl.util.jmx.annotation.ManagedAttribute;
import com.hzgj.bcl.util.jmx.annotation.ManagedOperation;
import com.hzgj.bcl.util.jmx.impl.MBeanFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.management.*;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.Socket;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;


/**
 * <p>Provides methods to register and unregister objects as JMX MBeans.</p>
 */
public final class Management {

    private static Logger logger = LoggerFactory.getLogger(Management.class);
    public static final String JMX_PORT_PROPERTY = "jmx.port";
    private static int JMX_PORT_STARTED = -1;

    private Management() {
    }

    public static class ConnectorServer {
        public static final String serviceUrl = "service:jmx:rmi://localhost/jndi/rmi://localhost:" + getJmxPort() + "/jmxrmi";

        public static void start() throws IOException {
            Registry reg = LocateRegistry.createRegistry(getJmxPort());
            reg.list();
            JMXServiceURL url = new JMXServiceURL(serviceUrl);
            JMXConnectorServer connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, Maps.newHashMap(), getMBeanServer());
            connectorServer.start();
            if (logger.isInfoEnabled()) {
                logger.info("JMX connector server started at:" + getJmxPort());
            }
        }

    }

    /**
     * <p>Registers an object with the specified <code>name</code> in the default <code>MBeanServer</code> (which is
     * retrieved using the <code>ManagementFactory.getPlatformServer()</code> method).</p>
     * <p>
     * <p>All the public attributes and methods annotated with {@link ManagedAttribute} and {@link ManagedOperation}
     * of the object's class (and the classes it descends from) will be exposed. The object must be annotated with
     * ManagedBean.</p>
     *
     * @param object the object that will be exposed as an MBean.
     * @param name   the name used to expose the object in the MBeanServer
     * @throws ManagementException if there is a problem creating or registering the MBean.
     */
    public static synchronized void register(Object object, String name) throws ManagementException {

        if (object == null) {
            throw new IllegalArgumentException("No object specified.");
        }
        MBeanServer mBeanServer = getMBeanServer();
        if (mBeanServer == null) {
            throw new ManagementException("No MBeanServer found.");
        }
        try {
            ObjectName objName = getObjectName(object, name);
            if (objName == null) {
                return;
            }
            if (mBeanServer.isRegistered(objName)) {
                logger.warn("unregistering already registered MBean: " + objName);
                try {
                    mBeanServer.unregisterMBean(objName);
                } catch (InstanceNotFoundException e) {
                    logger.error("failed to unregister MBean " + e.getMessage());
                }
            }

            DynamicMBean mBean = MBeanFactory.createMBean(object);
            mBeanServer.registerMBean(mBean, objName);
        } catch (Exception e) {
            throw new ManagementException(e);
        }
    }

    /**
     * <p>Unregisters an MBean with the specified <code>name</code> if it exists in the default
     * <code>MBeanServer</code> (which is retrieved using the <code>ManagementFactory.getPlatformServer()</code>
     * method).</p>
     *
     * @param name the name with which the MBean was registered.
     * @throws ManagementException wraps any unexpected exception unregistering the MBean.
     */
    public static void unregister(String name) throws ManagementException {

        if (name == null || "".equals(name)) {
            throw new IllegalArgumentException("No name specified.");
        }

        MBeanServer mBeanServer = getMBeanServer();
        if (mBeanServer == null) {
            throw new ManagementException("No MBeanServer found.");
        }

        try {
            mBeanServer.unregisterMBean(new ObjectName(name));
        } catch (InstanceNotFoundException e) {

        } catch (Exception e) {
            throw new ManagementException(e);
        }

    }

    public static synchronized boolean isRegistered(String name) throws ManagementException {

        if (name == null || "".equals(name)) {
            throw new IllegalArgumentException("No name specified.");
        }

        MBeanServer mBeanServer = getMBeanServer();
        if (mBeanServer == null) {
            throw new ManagementException("No MBeanServer found.");
        }

        try {
            return mBeanServer.isRegistered(new ObjectName(name));
        } catch (Exception e) {
            throw new ManagementException(e);
        }
    }

    private static ObjectName getObjectName(Object obj, String name) throws MalformedObjectNameException {
        MBean resource = obj.getClass().getAnnotation(MBean.class);
        if (resource == null) {
            return null;
        }
        if (name != null && !name.isEmpty()) {
            return new ObjectName(name);
        } else if (resource.objectName() != null && !resource.objectName().isEmpty()) {
            return new ObjectName(resource.objectName());
        } else {
            throw new MalformedObjectNameException(obj + " of class " + obj.getClass() + " has an invalid object name");
        }
    }

    public static int getJmxPort() {
        if (JMX_PORT_STARTED != -1) {
            return JMX_PORT_STARTED;
        }
        String port = System.getProperty(JMX_PORT_PROPERTY, "10990");
        int _port = Integer.parseInt(port);
        while (true) {
            try {
                Socket socket = new Socket(InetAddress.getLocalHost(), _port);
                socket.close();
                _port++;
            } catch (IOException e) {
                JMX_PORT_STARTED = _port;
                break;
            }
        }
        return _port;
    }

    public static MBeanServer getMBeanServer() {
        return ManagementFactory.getPlatformMBeanServer();
    }
}
